package com.gamefx.mvc
{
	import flash.utils.getQualifiedClassName;
	
	import com.gamefx.mvc.classes.Notification;
	import com.gamefx.mvc.classes.Observer;

	public class Facade
	{
		private static var instance:Facade;
		private var injector:Injector;

		internal var modelMap:Object;
		internal var viewMap:Object;
		internal var commandMap:Object;
		internal var observerMap:Object;
		


		public function Facade()
		{
			if (instance != null)
			{
				throw new Error("Facade Singleton already constructed!");
			}
			
			instance = this;

			modelMap = {};
			viewMap = {};
			commandMap = {};
			observerMap = {};
			
			injector = new Injector();
		}

		public static function getInstance():Facade
		{
			if (instance == null)
			{
				instance = new Facade();
			}
			
			return instance;
		}



		//--------------------------------------------------------------------------
		//
		//  Methods
		//
		//--------------------------------------------------------------------------
		
		public static function Name(target:*):String
		{
			return getQualifiedClassName(target).replace("::", ".");
		}

		//----------------------------------
		//  model
		//----------------------------------

		public function registerModel(model:Model):void
		{
			modelMap[model.getModelName()] = model;
			injector.inject(model);
			model.onRegister();
		}

		public function retrieveModel(modelName:String):Model
		{
			return modelMap[modelName];
		}

		public function hasModel(modelName:String):Boolean
		{
			return modelMap[modelName] != null;
		}

		public function removeModel(modelName:String):Model
		{
			var model:Model = modelMap[modelName] as Model;
			if (model)
			{
				modelMap[modelName] = null;
				delete modelMap[modelName];
				model.onRemove();
			}
			return model;
		}


		//----------------------------------
		//  view
		//----------------------------------

		public function registerView(view:View):void
		{
			viewMap[view.getViewName()] = view;
			injector.inject(view);
			view.onRegister();
			injector.subscribe(view);
		}

		public function retrieveView(viewName:String):View
		{
			return viewMap[viewName];
		}

		public function hasView(viewName:String):Boolean
		{
			return viewMap[viewName] != null;
		}

		public function removeView(viewName:String):View
		{
			var view:View = viewMap[viewName] as View;

			if (view)
			{
				viewMap[viewName] = null;
				delete viewMap[viewName];
				view.onRemove();
			}

			return view;
		}


		//----------------------------------
		//  controller
		//----------------------------------

		public function registerCommand(notificationName:String, commandClassRef:Class):void
		{
			if (commandMap[notificationName] == null)
			{
				registerObserver(notificationName, new Observer(executeCommand, this));
			}
			commandMap[notificationName] = commandClassRef;
		}

		public function hasCommand(notificationName:String):Boolean
		{
			return commandMap[notificationName] != null;
		}

		public function executeCommand(notification:Notification):void
		{
			var commandClassRef:Class = commandMap[notification.getName()];
			if (commandClassRef == null)
			{
				return;
			}

			var commandInstance:Command = new commandClassRef();
			injector.inject(commandInstance);
			commandInstance.execute(notification);
		}

		public function removeCommand(notificationName:String):void
		{
			if (hasCommand(notificationName))
			{
				removeObserver(notificationName, this);

				commandMap[notificationName] = null;
				delete commandMap[notificationName];
			}
		}


		//----------------------------------
		//  observer
		//----------------------------------

		internal function registerObserver(notificationName:String, observer:Observer):void
		{
			var observers:Array = observerMap[notificationName];

			if (observers)
			{
				observers.push(observer);
			}
			else
			{
				observerMap[notificationName] = [observer];
			}
		}

		internal function removeObserver(notificationName:String, notifyContext:Object):void
		{
			var observers:Array = observerMap[notificationName] as Array;
			var observer:Observer;

			for (var i:int = 0; i < observers.length; i++)
			{
				observer = observers[i] as Observer;
				if (observer.compareNotifyContext(notifyContext) == true)
				{
					observers.splice(i, 1);
					break;
				}
			}

			if (observers.length == 0)
			{
				delete observerMap[notificationName];
			}
		}

		internal function notifyObservers(notification:Notification):void
		{
			if (observerMap[notification.getName()] != null)
			{
				var observers_ref:Array = observerMap[notification.getName()] as Array;
				var observers:Array = new Array();
				var observer:Observer;

				for (var i:int = 0; i < observers_ref.length; i++)
				{
					observer = observers_ref[i] as Observer;
					observers.push(observer);
				}

				for (i = 0; i < observers.length; i++)
				{
					observer = observers[i] as Observer;
					observer.notifyObserver(notification);
				}
			}
		}

		public function sendNotification(notificationName:String, body:Object = null, type:String = null):void
		{
			notifyObservers(new Notification(notificationName, body, type));
		}
	}
}
